/*
 * Alloy Analyzer
 * Copyright (c) 2002 Massachusetts Institute of Technology
 *
 * The Alloy Analyzer is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * The Alloy Analyzer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the Alloy Analyzer; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package alloy.gui;

import java.awt.Rectangle;

import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultHighlighter;
import javax.swing.text.Highlighter;

import alloy.ast.Location;

/**
 * Highlights extents of Alloy specification corresponding to a given {@link Location}.
 * This is used for highlighting regions of text corresponding to the AST node clicked
 * in the {@link SwingReturnVisitor Alloy evaluator}.
 */
class LocDisplayer {
    private final LocatableTextArea textArea;
    private final Highlighter highlighter;

    LocDisplayer(LocatableTextArea textArea_) {
    textArea = textArea_;
    highlighter = new DefaultHighlighter();
    textArea.setHighlighter(highlighter);
    }

    /**
     * clear all highlights originating from this highlighter
     */
    void clearHighlights () {
    highlighter.removeAllHighlights();
    }

    /**
     * Highlight the given {@link Location}, loading the correct Alloy spec file
     * into the GUI if necessary.
     * @param loc_ the {@link Location} to highlight; if <code>null</code>, any current
     *             higlight is removed.
     * @param painter the {@link Highlighter.HighlightPainter} to use when painting the location
     * @param showText whether we should scroll the location into the visible portion of the text
     *          area.
     *
     * Returns a String of the text that was highlighted.
     */
    String showLoc(Location loc_, Highlighter.HighlightPainter painter, boolean showText) {
    if (loc_ == null || loc_.getFileName() == null) {
        // clear selection
        highlighter.removeAllHighlights();
        return null;
    }
    String currentFileName = textArea.getFilePath();
    if ((currentFileName == null || !currentFileName.equals(loc_.getFileName())) &&
        !loc_.getFileName().equals(Location.NOT_FROM_FILE)) {
        // load a new Alloy spec into the GUI.  if the file currently
        // open has been modified and not saved, the user may decide
        // to keep it open, in which case highlighting will not be done
        // (though no error will be returned to the caller of showLoc()).
        if (!textArea.readFile(loc_.getFileName()))
        return null;
    }

    final int locBegLine = loc_.getBeginLine();
    final int locBegCol = loc_.getBeginColumn();
    final int locEndLine = loc_.getEndLine();
    final int locEndCol = loc_.getEndColumn();
    String txt = textArea.getText();

    //final int selStart = ((Integer)lineStarts.get(locBegLine-1)).intValue() + locBegCol;
    //final int selEnd = ((Integer)lineStarts.get(locEndLine-1)).intValue() + locEndCol;
    final int selStart = lineAndColumnToIndex(locBegLine, locBegCol, txt);
    final int selEnd = lineAndColumnToIndex(locEndLine, locEndCol, txt) + 1;

    try {
        highlighter.addHighlight(
                     selStart,
                     selEnd,
                     painter);
    } catch (BadLocationException ble_) {
    }
    //      Rectangle r = new Rectangle(Math.min(locBegCol, locEndCol), locBegLine,
    //                  Math.abs(locEndCol - locBegCol) + 1,
    //                  locEndLine - locBegLine + 1);
    if (showText) {
        try {
        Rectangle rStart = textArea.modelToView(selStart);
        Rectangle rEnd = textArea.modelToView(selEnd);
        textArea.scrollRectToVisible(rStart.union(rEnd));
        } catch (BadLocationException ble_) {
        }
    }

    String selText = null;
    try {
        selText = textArea.getText (selStart, selEnd - selStart);
    } catch (BadLocationException ex_) {}

    return selText;
    } // showLoc()

    /**
     * @param lineNumber
     * @param columnNumber
     * @param text
     * @return
     */
    static private int lineAndColumnToIndex(
                        int lineNumber,
                        int columnNumber,
                        String text) {
    char textArray[] = text.toCharArray();
    int newLineCount = 1;
    int carReturnCount = 0;
    int index;

    if ((lineNumber > 0)
        && //Make sure line and column numbers are valid
        (columnNumber > 0)) {
        int i;
        for (i = 0; newLineCount < lineNumber; i++) {
        if (textArray[i] == '\n') {
            newLineCount++;
        } else if (textArray[i] == '\r') {
            carReturnCount++;
        }
        }
        // handle tabs in line
        int fromLastTab = 0; // to figure out how many columns a tab will take
        for (int j = 0; j < columnNumber; j++) {
	    if (textArray.length == (i+j) && j == (columnNumber - 1)) {
		// if line & column number refer to end of text, we're done
		break;
	    }
	    
	    // make sure that i+j is within bounds
	    if (i+j >= textArray.length) { 
		return -1; }

	    if (textArray[i+j] == '\t') {
		// subtract from columnNumber the extra
		// amount that JavaCC added to it
		columnNumber -= 8 - (fromLastTab%8) - 1;
		fromLastTab = 0;
	    } else { fromLastTab++; }
        }
        index = i + columnNumber - carReturnCount - 1;
        return index;
    } else {
        // System.out.println("Invalid location");
        return -1;
    }
    }
}
